/*
 * Copyright (C) 2015 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package hos.retrofit;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Objects;

final class DefaultCallAdapterFactory extends RetrofitCallAdapter.Factory {
    private final ExecutorRun callbackExecutor;

    DefaultCallAdapterFactory(ExecutorRun callbackExecutor) {
        this.callbackExecutor = callbackExecutor;
    }

    @Override
    public RetrofitCallAdapter<?, ?> get(
            Type returnType, Annotation[] annotations, Retrofit retrofit) {
        if (getRawType(returnType) != RetrofitCall.class) {
            return null;
        }
        if (!(returnType instanceof ParameterizedType)) {
            throw new IllegalArgumentException(
                    "Call return type must be parameterized as Call<Foo> or Call<? extends Foo>");
        }
        final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);

        final ExecutorRun executor = callbackExecutor;

        return new RetrofitCallAdapter<Object, RetrofitCall<?>>() {
            @Override
            public Type responseType() {
                return responseType;
            }

            @Override
            public RetrofitCall<Object> adapt(RetrofitCall<Object> call) {
                return executor == null ? call : new ExecutorCallbackCall<>(executor, call);
            }
        };
    }

    static final class ExecutorCallbackCall<T> implements RetrofitCall<T> {
        final ExecutorRun callbackExecutor;
        final RetrofitCall<T> delegate;

        ExecutorCallbackCall(ExecutorRun callbackExecutor, RetrofitCall<T> delegate) {
            this.callbackExecutor = callbackExecutor;
            this.delegate = delegate;
        }

        @Override
        public void enqueue(final RetrofitCallback<T> callback) {
            Objects.requireNonNull(callback, "callback == null");
            delegate.enqueue(new RetrofitCallback<T>() {
                @Override
                public void onResponse(RetrofitCall<T> call, final T response) {
                    if (callbackExecutor == null) {
                        if (delegate.isCanceled()) {
                            // Emulate OkHttp's behavior of throwing/delivering an IOException on
                            // cancellation.
                            callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
                        } else {
                            callback.onResponse(ExecutorCallbackCall.this, response);
                        }
                        return;
                    }
                    callbackExecutor.callback(new Runnable() {
                        @Override
                        public void run() {
                            if (delegate.isCanceled()) {
                                // Emulate OkHttp's behavior of throwing/delivering an IOException on
                                // cancellation.
                                callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
                            } else {
                                callback.onResponse(ExecutorCallbackCall.this, response);
                            }
                        }
                    });
                }

                @Override
                public void onFailure(RetrofitCall<T> call, final Throwable t) {
                    if (callbackExecutor == null) {
                        callback.onFailure(ExecutorCallbackCall.this, t);
                        return;
                    }
                    callbackExecutor.callback(new Runnable() {
                        @Override
                        public void run() {
                            callback.onFailure(ExecutorCallbackCall.this, t);
                        }
                    });
                }
            });
        }

        @Override
        public boolean isExecuted() {
            return delegate.isExecuted();
        }

        @Override
        public T execute() throws IOException {
            return delegate.execute();
        }

        @Override
        public void cancel() {
            delegate.cancel();
        }

        @Override
        public boolean isCanceled() {
            return delegate.isCanceled();
        }

        @SuppressWarnings("MethodDoesntCallSuperMethod")
        @Override
        public RetrofitCall<T> clone() {
            return new ExecutorCallbackCall<>(callbackExecutor, delegate.clone());
        }

    }
}
